#include "Contexto.h"
#include <string>

/**
* @param orden Indica la cantidad de simbolos que se deben tener en cuenta 
* en el valor del contexto.
*/
Contexto::Contexto(Orden orden,Modelo *modeloCero):
    orden(orden),
    modeloCero(modeloCero),
    valorContexto(),
    listadoModelos(),
    modeloCeroYaDevuelto(true)
{
	if (this->modeloCero==NULL)
		this->modeloCero=new Modelo();
}

Contexto::~Contexto()
{
	delete this->modeloCero;
}

/**
* Agrega un simbolo al final del valor del contexto y se encarga de 
* eliminar el primer simbolo del valor del contexto en caso de 
* corresponder.
* @param simbolo Simbolo a ser agregado.
*/
void Contexto::addSimbolo(const Simbolo simbolo)
{
    // En caso que la longitud del valor del contexto sea mayor que su orden...
    if (this->valorContexto.size()==this->orden)
        // ...se elimina el primer simbolo del valor del contexto.
        this->valorContexto.pop_front();

    // Se agrega el simbolo al final del valor del contexto el simbolo pasado.
    this->valorContexto.push_back(simbolo);
    
}

/**
* @pre Debe existir el camino de simbolos en el arbol de modelos.
* Devuelve el modelo para el valor del contexto. Siempre devuelve el modelo.
* Si no existe lo crea en las estructura de modelos.
* @param valorContexto Camino a seguir en el arbol de modelos.
*/
Modelo *Contexto::getModelo(const ValorContexto &valorContexto)
{
    // Obtenemos un iterador al primer simbolo del valor del contexto
    ValorContexto::const_iterator itSimbolo=valorContexto.begin();
    
    // Inicializamos el modelo actual
    Modelo *modelo=this->modeloCero;

    // Verificamos que haya símbolos en el valor del contexto
    bool seguir = (itSimbolo!=valorContexto.end());
    
    while (seguir)
    {
    	try
    	{
        	modelo=modelo->crearModeloSiguienteOrdenSiNoExiste(*itSimbolo);
    	}
    	catch(std::string &s)
    	{
    		throw std::string(
    				"El valor de contexto pasado no existe en el arbol de modelos");
    	}
        
        itSimbolo++;
        
        // Verificamos que haya más símbolos en el valor del contexto
        seguir = (itSimbolo!=valorContexto.end());
    }
    
    return modelo;
}

/**
* Obtiene un listado de los modelos que deben ser consultados ordenados 
* por su Orden de menor a mayor segun el valor del contexto actual.
*/
Contexto::ListadoModelos &Contexto::getModelos()
{
    Contexto::ValorContexto valorContextoPorOrden;

    // Vaciamos el listado de modelos a ser devuelto.
    this->listadoModelos.clear();
    
    // Agregamos el modelo cero al principio del listado
    this->listadoModelos.push_back(this->modeloCero);

    // Obtenemos un iterador al ultimo simbolo del valor del contexto
    Contexto::ValorContexto::const_reverse_iterator ritSimbolo=
        this->valorContexto.rbegin();
    
    bool seguir = (ritSimbolo!=this->valorContexto.rend());
    
    // Aca la idea es la siguiente:
    // Si tenemos por ejemplo el valor de contexto ABC, vamos a obtener los 
    // siguientes modelos:
    // 1) C (orden 1)
    // 2) BC (orden 2)
    // 3) ABC (orden 3)
    
    while (seguir)
    {
        valorContextoPorOrden.push_front(*ritSimbolo);
        
        Modelo *modelo=this->getModelo(valorContextoPorOrden);
        
        listadoModelos.push_back(modelo);
        
        ritSimbolo++;
        seguir = (ritSimbolo!=this->valorContexto.rend());
    }
    
    return this->listadoModelos;
}

/**
 * Para el contexto actual devuelve el modelo de mayor orden, e inicializa
 * la iteración hacia atrás. Para obtener los sucesivos modelos, hasta el
 * modelo cero, para el contexto actual, llamar a getModeloAnteriorOrden.
 * @return Modelo de mayor orden.
 */
Modelo *Contexto::getModeloMayorOrden()
{
	Modelo *modelo;

	if (this->valorContexto.empty())
	{
		this->modeloCeroYaDevuelto=true;
		modelo=this->modeloCero;
	}
	else
	{
		this->modeloCeroYaDevuelto=false;
		this->valorContextoRecorrido=this->valorContexto;
		modelo=this->getModelo(this->valorContextoRecorrido);
		this->valorContextoRecorrido.pop_front();
	}

	return modelo;
}

/**
 * @pre Antes de llamar este método debe haber sido llamado el método
 * getModeloMayorOrden.
 * Llamado sucecivas veces luego de llamar getModeloMayorOrden. Va obteniendo
 * con cada llamado un modelo de orden anterior hasta devolver el modelo cero
 * y finalmente con la última llamada devolvera null.
 * @return Modelo de orden menor al obtenido con getModeloMayorOrden o
 * getModeloAnteriorOrden
 */
Modelo *Contexto::getModeloAnteriorOrden()
{
	Modelo *modelo;

	if (this->modeloCeroYaDevuelto)
		modelo=NULL;
	else
	{
		if (this->valorContextoRecorrido.empty())
		{
			this->modeloCeroYaDevuelto=true;
			modelo=this->modeloCero;
		}
		else
		{
			modelo=this->getModelo(this->valorContextoRecorrido);
			this->valorContextoRecorrido.pop_front();
		}
	}

	return modelo;
}
